{-----------------------------------------------------------------
 
  this module contains the functionality to search for packages
  that have been emerged with a particular USE flag

  (c) 2008-2009 Markus Dittrich 
 
  This program is free software; you can redistribute it 
  and/or modify it under the terms of the GNU General Public 
  License Version 3 as published by the Free Software Foundation. 
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License Version 3 for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 59 Temple Place - Suite 330, 
  Boston, MA 02111-1307, USA.

--------------------------------------------------------------------}

-- | this modules handles request to check installed packages
-- for consistency
module Drivers.Check ( check_all_packages
                     , check_package
                     ) where


-- imports
import qualified Data.ByteString.Char8 as BC(unpack)
import Data.Digest.OpenSSL.MD5(md5sum)
import Prelude
import System.Directory(getModificationTime)
import System.FilePath.Posix((</>))
import System.Time(ClockTime(TOD), diffClockTimes, noTimeDiff)


-- local imports
import Parsers.Content(FileType(..), PackageContentItem(..),
  retrieve_package_content)
import Helpers.FileIO(retrieve_directory_contents, try_read_file)
import Helpers.PrettyPrint(Color(..),  putColorStr, putColorStrLn)



-- | data structure for handling file checking results 
data PkgStatus = Good | NotAccessible | BadMD5 | BadModTime |
                 BadMD5ModTime
               deriving(Eq)


data PackageCheckResult = PackageCheckResult {
    pkgCategory :: String
  , pkgName     :: String
  , file        :: String
  , status      :: PkgStatus
  }


defaultPackageCheckResult :: PackageCheckResult
defaultPackageCheckResult = PackageCheckResult {
    pkgCategory = ""
  , pkgName     = ""
  , file        = ""
  , status      = Good
  }
                              
    


-- | check all packages for consistency 
check_all_packages :: [FilePath] -> IO ()
check_all_packages categories = 
  
  mapM (\path -> retrieve_directory_contents path
         >>= mapM (check_package path)  
        ) categories
  >>= return . concat . concat 
  >>= print_non_accessible 



-- | check a single package for consistency
check_package :: FilePath -> String -> IO [PackageCheckResult]
check_package path theName = 
  
  (retrieve_package_content $ path </> theName)
  >>= return . filter (\x -> fileType x == Obj)
  >>= mapM (check_content resultContainer)
  >>= \result -> mapM_ print_result result
  >> (return $ filter (\x -> status x == NotAccessible) result)

  where
    resultContainer = defaultPackageCheckResult {
                        pkgCategory = path 
                      , pkgName     = theName
                      }



-- | check content of an individual package
check_content :: PackageCheckResult -> PackageContentItem 
              -> IO PackageCheckResult
check_content inputContainer item =

  let
    targetTime     = TOD (read . BC.unpack $ modTime item) 0
    theName        = BC.unpack $ fileName item
    targetMD5      = BC.unpack $ md5 item
    namedContainer = inputContainer { file = theName }
  in
    try_read_file theName
    >>= \content ->
      case content of
        Nothing -> return namedContainer { status = NotAccessible }
        Just c  ->
 
          getModificationTime theName
          >>= \time ->
          let 
            contentMD5  = md5sum c
            contentBad  = contentMD5 /= targetMD5
            timeBad     = (diffClockTimes time targetTime) 
                          /= noTimeDiff
          in
            if contentBad && timeBad
            then 
              return $ namedContainer { status = BadMD5ModTime }
            else
              if contentBad
              then
                return $ namedContainer { status = BadMD5 }
              else
                if timeBad
                then
                  return $ namedContainer { status = BadModTime }
                else
                  return inputContainer



-- | pretty printer for listing package check results
print_result :: PackageCheckResult -> IO ()
print_result result = 

  case status result of
    BadMD5ModTime -> putColorStr Cyan "bad MD5 + mod time: "
                     >> print_info
    BadMD5        -> putColorStr Red "bad MD5: " 
                     >> print_info
    BadModTime    -> putColorStr Yellow "bad mod time: " 
                     >> print_info
    _             -> return ()


  where
    pkg  = (pkgCategory result) </> (pkgName result)
    f    = file result  

    print_info = (putStr $ f ++ " [")
                 >> putColorStr Blue pkg
                 >> putStrLn "]"



-- | pretty printer for listing non accessible files
print_non_accessible :: [PackageCheckResult] -> IO ()
print_non_accessible items = 
  
  (putColorStrLn Red $ "\n\n*** The following files could not be \
                       \accessed")
  >> mapM_ list_file items

  where
    list_file x = let 
                    theFile = file x
                    thePkg  = pkgName x
                  in
                    (putStr $ theFile ++ " [")
                    >> (putColorStr Blue thePkg)
                    >> putStrLn "]"
                    
